package com.fary.context.annotation;

import com.fary.beans.factory.annotation.AnnotatedBeanDefinition;
import com.fary.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import com.fary.beans.factory.annotation.Lookup;
import com.fary.beans.factory.config.BeanDefinition;
import com.fary.beans.factory.support.BeanDefinitionRegistry;
import com.fary.context.ResourceLoaderAware;
import com.fary.context.index.CandidateComponentsIndex;
import com.fary.context.index.CandidateComponentsIndexLoader;
import com.fary.core.SpringException;
import com.fary.core.annotation.AnnotationUtils;
import com.fary.core.env.Environment;
import com.fary.core.env.EnvironmentCapable;
import com.fary.core.env.StandardEnvironment;
import com.fary.core.io.Resource;
import com.fary.core.io.ResourceLoader;
import com.fary.core.io.ResourcePatternUtils;
import com.fary.core.io.support.PathMatchingResourcePatternResolver;
import com.fary.core.io.support.ResourcePatternResolver;
import com.fary.core.type.AnnotationMetadata;
import com.fary.core.type.classreading.CachingMetadataReaderFactory;
import com.fary.core.type.classreading.MetadataReader;
import com.fary.core.type.classreading.MetadataReaderFactory;
import com.fary.core.type.filter.AnnotationTypeFilter;
import com.fary.core.type.filter.AssignableTypeFilter;
import com.fary.core.type.filter.TypeFilter;
import com.fary.stereotype.Component;
import com.fary.stereotype.Indexed;
import com.fary.util.Assert;
import com.fary.util.ClassUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;

public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {

    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";


    protected final Log logger = LogFactory.getLog(getClass());

    private String resourcePattern = DEFAULT_RESOURCE_PATTERN;

    private final List<TypeFilter> includeFilters = new LinkedList<>();

    private final List<TypeFilter> excludeFilters = new LinkedList<>();

    private Environment environment;

    private ConditionEvaluator conditionEvaluator;

    private ResourcePatternResolver resourcePatternResolver;

    private MetadataReaderFactory metadataReaderFactory;

    private CandidateComponentsIndex componentsIndex;

    public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
        if (useDefaultFilters) {
            registerDefaultFilters();
        }
        setEnvironment(environment);
        setResourceLoader(null);
    }

    public void setEnvironment(Environment environment) {
        Assert.notNull(environment, "Environment must not be null");
        this.environment = environment;
        this.conditionEvaluator = null;
    }

    @Override
    public final Environment getEnvironment() {
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
        return this.environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
        this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
    }

    protected BeanDefinitionRegistry getRegistry() {
        return null;
    }

    public void setResourcePattern(String resourcePattern) {
        Assert.notNull(resourcePattern, "'resourcePattern' must not be null");
        this.resourcePattern = resourcePattern;
    }

    private ResourcePatternResolver getResourcePatternResolver() {
        if (this.resourcePatternResolver == null) {
            this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
        }
        return this.resourcePatternResolver;
    }

    public void addIncludeFilter(TypeFilter includeFilter) {
        this.includeFilters.add(includeFilter);
    }

    public void addExcludeFilter(TypeFilter excludeFilter) {
        this.excludeFilters.add(0, excludeFilter);
    }

    public final MetadataReaderFactory getMetadataReaderFactory() {
        if (this.metadataReaderFactory == null) {
            this.metadataReaderFactory = new CachingMetadataReaderFactory();
        }
        return this.metadataReaderFactory;
    }

    /**
     * 注册默认的过滤器，@Component、@ManagedBean、@Named
     */
    @SuppressWarnings("unchecked")
    protected void registerDefaultFilters() {
        this.includeFilters.add(new AnnotationTypeFilter(Component.class));
        ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
        try {
            this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
            logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
        } catch (ClassNotFoundException ex) {
            // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
        }
        try {
            this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
            logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
        } catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
    }

    /**
     * 查找组件
     */
    public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
            return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
        } else {
            return scanCandidateComponents(basePackage);
        }
    }

    private boolean indexSupportsIncludeFilters() {
        for (TypeFilter includeFilter : this.includeFilters) {
            if (!indexSupportsIncludeFilter(includeFilter)) {
                return false;
            }
        }
        return true;
    }

    private boolean indexSupportsIncludeFilter(TypeFilter filter) {
        // 注解类型过滤：注解被@Indexed修饰，或者是以 javax 开头
        if (filter instanceof AnnotationTypeFilter) {
            Class<? extends Annotation> annotation = ((AnnotationTypeFilter) filter).getAnnotationType();
            return (AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, annotation) ||
                    annotation.getName().startsWith("javax."));
        }
        // 类型过滤：类被@Indexed修饰
        if (filter instanceof AssignableTypeFilter) {
            Class<?> target = ((AssignableTypeFilter) filter).getTargetType();
            return AnnotationUtils.isAnnotationDeclaredLocally(Indexed.class, target);
        }
        return false;
    }

    /**
     * 通过Index索引快速添加
     */
    private Set<BeanDefinition> addCandidateComponentsFromIndex(CandidateComponentsIndex index, String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            Set<String> types = new HashSet<>();
            for (TypeFilter filter : this.includeFilters) {
                String stereotype = extractStereotype(filter);
                if (stereotype == null) {
                    throw new IllegalArgumentException("Failed to extract stereotype from " + filter);
                }
                types.addAll(index.getCandidateTypes(basePackage, stereotype));
            }
            for (String type : types) {
                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(type);
                if (isCandidateComponent(metadataReader)) {
                    AnnotatedGenericBeanDefinition sbd = new AnnotatedGenericBeanDefinition(metadataReader.getAnnotationMetadata());
                    if (isCandidateComponent(sbd)) {
                        logger.debug("Using candidate component class from index: " + type);
                        candidates.add(sbd);
                    } else {
                        logger.debug("Ignored because not a concrete top-level class: " + type);
                    }
                } else {
                    logger.trace("Ignored because matching an exclude filter: " + type);
                }
            }
        } catch (IOException ex) {
            throw new SpringException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

    private String extractStereotype(TypeFilter filter) {
        if (filter instanceof AnnotationTypeFilter) {
            return ((AnnotationTypeFilter) filter).getAnnotationType().getName();
        }
        if (filter instanceof AssignableTypeFilter) {
            return ((AssignableTypeFilter) filter).getTargetType().getName();
        }
        return null;
    }

    /**
     * 1.判断排除过滤器有没有匹配上，只要匹配返回false
     * 2.判断包含过滤器有没有匹配上，匹配上了返回true
     * 3.都没有匹配上就返回false
     */
    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }

    private boolean isConditionMatch(MetadataReader metadataReader) {
        if (this.conditionEvaluator == null) {
            this.conditionEvaluator =
                    new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
        }
        return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
    }

    /**
     * 判断是否是想要的组件
     * 具体类，或者抽象类（含有被@Lookup注解修饰的方法）
     */
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
    }

    private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern;
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            for (Resource resource : resources) {
                logger.trace("Scanning " + resource);
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                        if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                            if (isCandidateComponent(sbd)) {
                                logger.debug("Identified candidate component class: " + resource);
                                candidates.add(sbd);
                            } else {
                                logger.debug("Ignored because not a concrete top-level class: " + resource);
                            }
                        } else {
                            logger.trace("Ignored because not matching any filter: " + resource);
                        }
                    } catch (Throwable ex) {
                        throw new SpringException("Failed to read candidate component class: " + resource, ex);
                    }
                } else {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        } catch (IOException ex) {
            throw new SpringException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage));
    }
}